package org.xtremeobjects.domain.aspects.states;

import java.lang.reflect.Field;

import org.xtremeobjects.domain.states.State;
import org.xtremeobjects.domain.states.StateMachineFactory;
import org.xtremeobjects.domain.states.StateMachineRuntime;
import org.xtremeobjects.domain.states.annotated.StateMachineAnnotatedClass;
import org.xtremeobjects.anotations.Hidden;
import org.xtremeobjects.anotations.Label;
import org.xtremeobjects.anotations.state.*;

public aspect StateSupportAspect {

	transient StateMachineRuntime StateSupport.smi = null;
	transient StateMachineAnnotatedClass StateSupport.clazz = null;
	transient Field StateSupport.stateField = null;

	public interface StateSupport {
		StateMachineRuntime getStateMachineInstance();
		Object getState1();
		void setState(Object state);
		Field getStateField();
		StateMachineAnnotatedClass getAnnotatedClass();
		/**/
	}

	@Hidden
	public StateMachineRuntime StateSupport.getStateMachineInstance() {
		return smi;
	}
	
	@Hidden
	public StateMachineAnnotatedClass StateSupport.getAnnotatedClass() {
		if(clazz==null) {
			clazz = new StateMachineAnnotatedClass(this.getClass());
		}
		return clazz;
	}
	
	@Hidden
	public Field StateSupport.getStateField() {
		if(stateField==null) {
			stateField = getAnnotatedClass().getStateField();
			stateField.setAccessible(true);
		}
		return stateField;
	}
	
	@Hidden
	public Object StateSupport.getState1() {
		Object ret=null;
		try {
			ret = getStateField().get( this );
		} catch(IllegalAccessException ex) {
		}
		return ret;
	}
	
	public void StateSupport.setState( Object state ) {
		try {
			getStateField().set( this, state );
		} catch(IllegalAccessException ex) {
		}
	}
	/**/
	
	pointcut construction(StateSupport s) :
		execution( new(..) ) && target (s) && within(StateSupport+);

	after(StateSupport s) : construction (s) {
		s.smi = StateMachineFactory.Instance.createInstanceForClass(s.getClass());
		s.smi.setContextObject( s );
		
		/**
		 * FIXME When an object is reconstitued from a persistence initial value
		 * for state is not sincrinized betwen smi & the field on the real object.
		 */
		
		if(s.getState1()==null)
			updateStateToObject(s);
		else
			updateObjectToState(s);
	}
	
	pointcut event(StateSupport s) :
		execution( @Event * *(..)) && target(s) && within(StateSupport+); 

	void around(StateSupport s) : event( s ) {
		proceed(s);
		s.getStateMachineInstance().handleEvent(thisJoinPointStaticPart.getSignature().getName());
		updateStateToObject(s);
	}
	
	pointcut stateField(StateSupport s) :
		get( @StateField * *) && target(s) && within(StateSupport+);
	
	before(StateSupport s) : stateField( s ) {
		System.out.println("stateField " + thisJoinPoint.toString());
	}

	void updateStateToObject(StateSupport s) {
		String stateName = s.getStateMachineInstance().getCurrentState().getId();
		for (Object stateConstant : s.getStateField().getType().getEnumConstants()) {
			if(stateConstant.toString().equals(stateName)) {
				s.setState(stateConstant);
				break;
			}
		}
	}
	
	void updateObjectToState(StateSupport s) {
		if(s.getState1()!=null) {
			State st = s.getStateMachineInstance().getStateMachine().findStateByName(s.getState1().toString());
			s.getStateMachineInstance().recoverStateFromPersistence(st);
		}
	}
}
